﻿#region Copyright (c) 2013-04, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

#if TESTS

using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;


namespace Amarok.Agents
{
	[TestFixture]
	public class Test_AgentPerformance
	{
		public sealed class AgentWithoutHandler : Agent
		{
			public AgentWithoutHandler(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}
		}

		public sealed class AgentWithExclusiveNonParallelMessageHandler : Agent
		{
			public AgentWithExclusiveNonParallelMessageHandler(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			public static Int32 HandlerCalled;

			[MessageHandler, ExclusiveExecution, NoParallelism]
			private void _HandleMessage(TestMessage message)
			{
				HandlerCalled++;
			}
		}

		public sealed class AgentWithExclusiveNonParallelOperationHandler : Agent
		{
			public AgentWithExclusiveNonParallelOperationHandler(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			public static Int32 HandlerCalled;

			[OperationHandler, ExclusiveExecution, NoParallelism]
			private String _HandleMessage(TestOperation message)
			{
				HandlerCalled++;
				return "DONE";
			}
		}

		public sealed class AgentWithConcurrentOperationHandler : Agent
		{
			public AgentWithConcurrentOperationHandler(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			public static Int32 HandlerCalled;

			[OperationHandler, ConcurrentExecution, ModerateParallelism]
			private String _HandleMessage(TestOperation message)
			{
				Interlocked.Increment(ref HandlerCalled);
				return "done";
			}
		}

		public sealed class PublisherAgent : Agent
		{
			public PublisherAgent(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			public void TestPublish(Message message)
			{
				base.Publish(message);
			}
		}

		public sealed class SubscriberAgent : Agent
		{
			public SubscriberAgent(AgentEnvironment environment, AgentOptions options)
				: base(environment, options)
			{
			}

			public static Int32 HandlerCalled;

			[InformationHandler, ExclusiveExecution, NoParallelism]
			private void _HandleMessage(TestInformation message)
			{
				Interlocked.Increment(ref HandlerCalled);
			}
		}

		public sealed class TestMessage : Message
		{
		}

		public sealed class TestOperation : Operation<String, Int32>
		{
		}

		public sealed class TestInformation : Information
		{
		}


		[SetUp]
		public void Setup()
		{
			AgentWithExclusiveNonParallelMessageHandler.HandlerCalled = 0;
			AgentWithExclusiveNonParallelOperationHandler.HandlerCalled = 0;
			AgentWithConcurrentOperationHandler.HandlerCalled = 0;
			SubscriberAgent.HandlerCalled = 0;
		}


		// --- TESTS ---


		[Test]
		public void Test_PostMessagesToAgentWithoutHandler()
		{
			var agent = Agent.Factory.SpawnAgent<AgentWithoutHandler>(
				new AgentEnvironment(), AgentOptions.Empty);

			var message = new TestMessage();
			TestHelper.TestDuration("Test_PostMessagesToAgentWithoutHandler",
				() =>
				{
					for (Int32 i = 0; i < 1000000; i++)
						agent.Post(message);

					Console.WriteLine("posted");

					agent.Dispose();

					Console.WriteLine("disposed");
				});
		}

		[Test]
		public void Test_PostMessagesToAgentWithExclusiveNonParallelHandler()
		{
			var agent = Agent.Factory.SpawnAgent<AgentWithExclusiveNonParallelMessageHandler>(
				new AgentEnvironment(), AgentOptions.Empty);

			var message = new TestMessage();
			var count = 1000000;

			TestHelper.TestDuration("Test_PostMessagesToAgentWithExclusiveNonParallelHandler",
				() =>
				{
					for (Int32 i = 0; i < count; i++)
						agent.Post(message);

					Console.WriteLine("posted");

					Helper.WaitForCondition(30000, 10, () =>
						AgentWithExclusiveNonParallelMessageHandler.HandlerCalled == count);

					Console.WriteLine("done");

					agent.Dispose();

					Console.WriteLine("disposed");
				});
		}

		[Test]
		public void Test_PostOperationsToAgentWithExclusiveNonParallelHandler()
		{
			var agent = Agent.Factory.SpawnAgent<AgentWithExclusiveNonParallelOperationHandler>(
				new AgentEnvironment(), AgentOptions.Empty);

			var count = 100000;
			var tasks = new Task<String>[count];

			TestHelper.TestDuration("Test_PostOperationsToAgentWithExclusiveNonParallelHandler",
				() =>
				{
					for (Int32 i = 0; i < count; i++)
						tasks[i] = agent.PostOperation(new TestOperation());

					Console.WriteLine("posted");

					Task.WaitAll(tasks);
					Assert.That(AgentWithExclusiveNonParallelOperationHandler.HandlerCalled, Is.EqualTo(count));

					Console.WriteLine("done");

					agent.Dispose();

					Console.WriteLine("disposed");
				});
		}

		[Test]
		public void Test_PostOperationsToAgentWithConcurrentParallelHandler()
		{
			var agent = Agent.Factory.SpawnAgent<AgentWithConcurrentOperationHandler>(
				new AgentEnvironment(), AgentOptions.Empty);

			var count = 100000;
			var tasks = new Task<String>[count];

			TestHelper.TestDuration("Test_PostOperationsToAgentWithConcurrentParallelHandler",
				() =>
				{
					for (Int32 i = 0; i < count; i++)
						tasks[i] = agent.PostOperation(new TestOperation());

					Console.WriteLine("posted");

					Task.WaitAll(tasks);
					Assert.That(AgentWithConcurrentOperationHandler.HandlerCalled, Is.EqualTo(count));

					Console.WriteLine("done");

					agent.Dispose();

					Console.WriteLine("disposed");
				});
		}


		[Test]
		public void Test_PublishInformationToNoSubscriber()
		{
			var publisher = (PublisherAgent)Agent.Factory.SpawnAgent<PublisherAgent>(
				new AgentEnvironment(), AgentOptions.Empty);

			var message = new TestInformation();
			TestHelper.TestDuration("Test_PublishInformationToNoSubscriber",
				() =>
				{
					for (Int32 i = 0; i < 1000000; i++)
						publisher.TestPublish(message);

					Console.WriteLine("published");

					Helper.Dispose(publisher);

					Console.WriteLine("disposed");
				});
		}

		[Test]
		public void Test_PublishInformationToSingleSubscriber()
		{
			var publisher = (PublisherAgent)Agent.Factory.SpawnAgent<PublisherAgent>(
				new AgentEnvironment(), AgentOptions.Empty);
			var subscriber = Agent.Factory.SpawnAgent<SubscriberAgent>(
				new AgentEnvironment(), AgentOptions.Empty);

			var count = 1000000;
			var message = new TestInformation();

			TestHelper.TestDuration("Test_PublishInformationToSingleSubscriber",
				() =>
				{
					publisher.Subscribe<TestInformation>(subscriber.ToAgentReference());

					Console.WriteLine("subscribed");

					for (Int32 i = 0; i < count; i++)
						publisher.TestPublish(message);

					Console.WriteLine("published");

					Helper.WaitForCondition(30000, 100, () => SubscriberAgent.HandlerCalled == count);

					Console.WriteLine("done");

					Helper.Dispose(publisher);
					Helper.Dispose(subscriber);

					Console.WriteLine("disposed");
				});
		}

		[Test]
		public void Test_PublishInformationToManySubscriber()
		{
			var publisher = (PublisherAgent)Agent.Factory.SpawnAgent<PublisherAgent>(
				new AgentEnvironment(), AgentOptions.Empty);

			var subscribers = new IAgent[1000];
			for (Int32 i = 0; i < subscribers.Length; i++)
			{
				subscribers[i] = Agent.Factory.SpawnAgent<SubscriberAgent>(
					new AgentEnvironment(), AgentOptions.Empty);
			}

			var count = 1000;
			var message = new TestInformation();

			TestHelper.TestDuration("Test_PublishInformationToManySubscriber",
				() =>
				{
					for (Int32 i = 0; i < subscribers.Length; i++)
						publisher.Subscribe<TestInformation>(subscribers[i].ToAgentReference());

					Console.WriteLine("subscribed");

					for (Int32 i = 0; i < count; i++)
						publisher.TestPublish(message);

					Console.WriteLine("published");

					Helper.WaitForCondition(30000, 100, () => SubscriberAgent.HandlerCalled == count * subscribers.Length);

					Console.WriteLine("done");

					Helper.Dispose(publisher);

					for (Int32 i = 0; i < subscribers.Length; i++)
						Helper.Dispose(subscribers[i]);

					Console.WriteLine("disposed");
				});
		}

	}
}

#endif
